package main

import (
	"fmt"
	"sync"
	"time"
)

/*
1、select类型switch case语句，但select的功能和我们linux操作系统提供的io的select epoll poll类型
2、select主要作用于多个channel，配合case来选择
3、select case：哪个分支就绪了就执行哪个，所有分支都就绪随机选择执行(随机的目的是：防止饥饿)
4、channel是多线程安全的，不需要lock，但是使用需要实例化
5、Go 的select语句是一种仅能用于channl发送和接收消息的专用语句，此语句运行期间是阻塞的；当select中没有case语句的时候，会阻塞当前的groutine。所以，有人也会说select是用来阻塞监听goroutine的。
select是Golang在语言层面提供的I/O多路复用的机制，其专门用来检测多个channel是否准备完毕：可读或可写
*/
//定义全局变量
var check bool
var lock1 sync.Mutex //定义互斥锁

func g1() {
	time.Sleep(3 * time.Second)
	lock1.Lock()
	defer lock1.Unlock()
	check = true
}

func g2() {
	time.Sleep(2 * time.Second)
	lock1.Lock()
	defer lock1.Unlock()
	check = true
}

func g3(ch chan bool) {
	time.Sleep(3 * time.Second)
	ch <- true
}

func g4(ch chan bool) {
	time.Sleep(2 * time.Second)
	ch <- true
}

func main() {
	/*
		有两个goroutine执行，如何让我们的主goroutine知道哪个执行完成了
	*/
	//方案一：采用共享变量的方式通信
	//go g1()
	//go g2()
	//for {
	//	if check {
	//		time.Sleep(20 * time.Microsecond)
	//		fmt.Println("有goroutine完成")
	//		return // return了才不会一直打印下去，for的死循环用return退出
	//	}
	//}

	//方案二：采用select来监控goroutine，并很多时候多个goroutine不会写同一个channel
	var ch3 = make(chan bool)
	var ch4 = make(chan bool)
	go g3(ch3)
	go g4(ch4)
	//超时时间的另外一种方式，time.NewTimer的C是个channel，可以配置select设置超时时间
	tim := time.NewTimer(6 * time.Second)
	for {
		// select case：哪个分支就绪了就执行哪个，所有分支都就绪随机选择执行(随机的目的是：防止饥饿)
		// 注意这里的select{}块是阻塞的，这个执行完后面的才可以执行
		select {
		case <-ch3:
			fmt.Println("g3完成！")
			return //退出for的死循环需要return
		case <-ch4:
			fmt.Println("g4完成！")
			return
		//time.NewTimer的C是个chnnel，可以设置多长时间才发送值，一般用于设置超时时间
		case <-tim.C:
			fmt.Println("timeout")
			return
		//由于上面的过程是阻塞的，首先执行这里，然后for循环再次进入，探测哪个分支就绪，这个可以不用
		default:
			time.Sleep(6 * time.Second)
			fmt.Println("默认走这里")
			//return
		}
	}

}
